package net.gdface.thrift;

import static com.facebook.swift.codec.metadata.FieldKind.THRIFT_FIELD;
import static com.google.common.base.Preconditions.*;
import static java.lang.String.format;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.facebook.swift.codec.ThriftStruct;
import com.facebook.swift.codec.metadata.ThriftExtraction;
import com.facebook.swift.codec.metadata.ThriftFieldExtractor;
import com.facebook.swift.codec.metadata.ThriftFieldInjection;
import com.facebook.swift.codec.metadata.ThriftFieldMetadata;
import com.facebook.swift.codec.metadata.ThriftInjection;
import com.facebook.swift.codec.metadata.ThriftMethodExtractor;
import com.facebook.swift.codec.metadata.ThriftMethodInjection;
import com.facebook.swift.codec.metadata.ThriftParameterInjection;
import com.facebook.swift.codec.metadata.ThriftStructMetadata;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.TypeParameter;
import com.google.common.reflect.TypeToken;

import net.gdface.utils.BaseTypeTransformer;

/**
 * thrift工具
 * @author guyadong
 *
 */
public class BaseThriftUtils {
	public static final Set<Class<?>> THRIFT_BUILTIN_KNOWNTYPES = 
	ImmutableSet.of(
			boolean.class,
			byte.class,
			double.class,
			short.class,
			int.class,
			long.class,
			String.class,
			ByteBuffer.class,
			void.class,
			Boolean.class,
			Byte.class,
			Short.class,
			Integer.class,
			Long.class,
			Double.class);
	public static final Map<Class<?>,Class<?>> CAST_TYPES = 
		ImmutableMap.<Class<?>,Class<?>>builder()
			.put(byte[].class,ByteBuffer.class)
			.put(Date.class,Long.class)
			.put(java.sql.Date.class,Long.class)
			.put(java.sql.Time.class,Long.class)
			.put(float.class,double.class)
			.put(Float.class,Double.class)
			.put(URI.class,String.class)
			.put(URL.class,String.class)
			.build();
	public static final String DECORATOR_PKG_SUFFIX="decorator";
	public static final String CLIENT_SUFFIX="client";
	public static final String DECORATOR_CLIENT_PKG_SUFFIX= DECORATOR_PKG_SUFFIX + "." + CLIENT_SUFFIX;
	/**
	 * gu.sql2java.BaseBean 的内置字段名
	 */
	public static final ImmutableSet<String> BASEBEAN_BUILTIN_FIELDS=ImmutableSet.of("initialized","modified");
	private static Class<?> baseBeanClass;
	static{
		try {
			baseBeanClass = Class.forName("gu.sql2java.BaseBean",false,BaseThriftUtils.class.getClassLoader());
		} catch (ClassNotFoundException e) {			
		}
	}
	public BaseThriftUtils() {
	}

	public static Class<?> getBaseBeanClass() {
		return baseBeanClass;
	}

	public static void setBaseBeanClass(Class<?> baseBeanClass) {
		BaseThriftUtils.baseBeanClass = baseBeanClass;
	}

	/**
	 * 获取{@code field}指定的字段值<br>
	 * 参见 com.facebook.swift.codec.internal.reflection.AbstractReflectionThriftCodec#getFieldValue(Object, ThriftFieldMetadata)
	 * 
	 * @param instance
	 * @param field
	 * @return 字段值
	 * @throws Exception
	 */
	public static TypeValue getFieldValue(Object instance, ThriftFieldMetadata field) throws Exception {
		try {
			if (field.getExtraction().isPresent()) {
				ThriftExtraction extraction = field.getExtraction().get();
				if (extraction instanceof ThriftFieldExtractor) {
					ThriftFieldExtractor thriftFieldExtractor = (ThriftFieldExtractor) extraction;
					Field f = thriftFieldExtractor.getField();
					return new TypeValue(f.getGenericType(),f.get(instance));
				} else if (extraction instanceof ThriftMethodExtractor) {
					ThriftMethodExtractor thriftMethodExtractor = (ThriftMethodExtractor) extraction;
					Method method = thriftMethodExtractor.getMethod();
					return new TypeValue(method.getGenericReturnType(),method.invoke(instance));
				}
				throw new IllegalAccessException("Unsupported field extractor type " + extraction.getClass().getName());
			}
			throw new IllegalAccessException("No extraction present for " + field);
		} catch (InvocationTargetException e) {
			if (e.getTargetException() != null) {
				Throwables.throwIfInstanceOf(e.getTargetException(), Exception.class);
			}
			throw e;
		}
	}

	public static boolean isThriftStruct(Type type){
		return type instanceof Class<?> 
			? ((Class<?>)type).isAnnotationPresent(ThriftStruct.class) 
			: false;
	}

	public static boolean isThriftDecorator(Type type){
		return type instanceof Class<?> 
				? ThriftDecorator.class.isAssignableFrom((Class<?>)type) 
				: false;
	}

	public static boolean isPrimitiveArray(Type type){
		if(type instanceof Class<?>){
			Class<?> clazz = (Class<?>)type;
			return clazz.isArray() && clazz.getComponentType().isPrimitive();
		}
		return false;
	}

	public static boolean isThriftBuildinType(Type type){		
		return THRIFT_BUILTIN_KNOWNTYPES.contains(type);
	}

	public static boolean isPrimitivefloat(Type type){
		return type == float.class;
	}
	public static boolean isfloat(Type type){
		return type == float.class || type == Float.class;
	}
	public static boolean isCastType(Type type){
		return  CAST_TYPES.containsKey(type);
	}

	public static boolean isException(Type type){
		return null == type 
				? false 
				: Exception.class.isAssignableFrom(TypeToken.of(type).getRawType());
	}
	public static <T>Constructor<T> getConstructor(Class<T> clazz,Class<?>...parameterTypes){
		try {
			return clazz.getConstructor(parameterTypes);
		} catch (NoSuchMethodException e) {
			return null;
		} 
	}
	public static <T> boolean hasConstructor(Class<T> clazz,Class<?>...parameterTypes){
		return getConstructor(clazz,parameterTypes) != null;
	}
	public static boolean isThriftException(Type type){
		return isException(type) && isThriftStruct(type);
	}
	public static boolean isThriftException(Type left, Type right){
		return isThriftException(left) && isThriftException(right);
	}

	public static boolean needTransformer(Type type){
		return ! isThriftBuildinType(type) && ! isfloat(type);
	}
	public static interface Action{
		void doClass(Class<?> type);
	}
	public static void traverseTypes(Type type,Action action){
		checkArgument(null !=action,"action is null");
		if(type instanceof Class<?>){
			action.doClass((Class<?>) type);
		}else if( type instanceof ParameterizedType){
			ParameterizedType paramType = (ParameterizedType)type;
			Type rawType = paramType.getRawType();
			Type[] typeArgs = paramType.getActualTypeArguments();
			traverseTypes(rawType,action);
			for(Type arg:typeArgs){
				traverseTypes(arg,action);
			}
		}else if (type instanceof GenericArrayType) {
			traverseTypes(((GenericArrayType) type).getGenericComponentType(),action);
		} else if (type instanceof TypeVariable) {
			for (Type t : ((TypeVariable<?>) type).getBounds()) {
				traverseTypes(t,action);
			}
		} else if (type instanceof WildcardType) {
			for (Type t : ((WildcardType) type).getLowerBounds()) {
				traverseTypes(t,action);
			}
			for (Type t : ((WildcardType) type).getUpperBounds()) {
				traverseTypes(t,action);
			}
		} else{
			throw new IllegalArgumentException(String.format("not allow type %s", type.toString()));
		}
	}
	@SuppressWarnings("serial")
	public static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken) {
		  return new TypeToken<Map<K, V>>() {}
		    .where(new TypeParameter<K>() {}, keyToken)
		    .where(new TypeParameter<V>() {}, valueToken);
	}
	@SuppressWarnings("serial")
	public static <T> TypeToken<List<T>> listToken(TypeToken<T> keyToken) {
		  return new TypeToken<List<T>>() {}
		    .where(new TypeParameter<T>() {}, keyToken);
	}
	@SuppressWarnings("serial")
	public static <T> TypeToken<Set<T>> setToken(TypeToken<T> keyToken) {
		  return new TypeToken<Set<T>>() {}
		    .where(new TypeParameter<T>() {}, keyToken);
	}

	/**
	 * 返回{@code type}是否有对应的decorator类型
	 * @param type
	 * @return 如果{@code type}有对应的decorator类型返回{@code true},否则返回{@code false}
	 * @see #getDecoratorType(Type)
	 */
	public static boolean hasDecoratorType(Type type){
		return getDecoratorType(type) !=null;
	}
	/**
	 * 返回{@code clazz}对应的装饰类
	 * @param type
	 * @return 如果没有装饰类则返回{@code null}
	 */
	@SuppressWarnings("unchecked")
	public static <T>Class<? extends ThriftDecorator<T>> getDecoratorType(Type type){
		if(!isThriftStruct(type)){
			return getDecoratorType((Class<T>)type);
		}
		return null;
	}
	/**
	 * 返回{@code clazz}对应的装饰类
	 * @param clazz
	 * @return 如果没有装饰类则返回{@code null}
	 */
	@SuppressWarnings("unchecked")
	public static <T,D extends ThriftDecorator<T>>Class<D> getDecoratorType(Class<T> clazz){
		if(!isThriftStruct(clazz)){
			String decoratorClazzName = clazz.getPackage().getName() 
					+ "."
					+ DECORATOR_CLIENT_PKG_SUFFIX 
					+ "." 
					+ clazz.getSimpleName();
			try {
				Class<?> decoratorClazz = Class.forName(decoratorClazzName);
				checkState(isThriftDecoratorPair(decoratorClazz,clazz),
						"%s must immplement %s",
						decoratorClazz.getName(),
						ThriftDecorator.class.getName());
				return (Class<D>) decoratorClazz;
			} catch (ClassNotFoundException e) {
			}
		}
		return null;
	}

	/**
	 * 判断 {@code left and right}之间是否为装饰类和被装饰类关系
	 * @param left 装饰类
	 * @param right 补装饰类
	 * @return 如果是装饰类和被装饰类关系返回{@code true},否则返回{@code false}
	 */
	public static <L,R>boolean isThriftDecoratorPair(Class<L>left,Class<R>right){
		try {
			return isThriftDecorator(left) 
					&& left.getMethod("delegate").getReturnType() == right;
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 判断 {@code right}是否为{@code left}对应的client端存根类型
	 * @param left
	 * @param right
	 * @return 如果是client端存根类型返回{@code true}否则返回{@code false}
	 */
	public static <L,R>boolean isThriftClientPair(Class<L>left,Class<R>right){
		return getMiddleClass(left,right)!=null;
	}
	/**
	 * 返回 {@code left & right}之间的decorator类型,如果没有找到decorator类型定义则返回{@code null}
	 * @param left 原始类型
	 * @param right {@code left}对应的client端存根类型
	 * @return decorator类型
	 */
	public static <L,M extends ThriftDecorator<L>,R>Class<M> getMiddleClass(Class<L>left,Class<R>right){
		Class<M> decoratorClass = getDecoratorType(left);
		if(null != decoratorClass && decoratorClass.getSimpleName().equals(left.getSimpleName())){
			return decoratorClass;
		}			
		return null;
	}
	/**
	 * 返回 {@code left & right}之间的decorator类型,如果{@code right}不为thrift struct则抛出异常
	 * @param left 原始类型
	 * @param right {@code left}对应的client端存根类型
	 * @return decorator类型
	 */
	@SuppressWarnings("unchecked")
	public static <L,M extends ThriftDecorator<L>,R>Class<M> getMiddleClassChecked(Class<L>left,Class<R>right){
		return (Class<M>) checkNotNull(
						getMiddleClass(left,right),
						"NOT FOUND decorator class for %s",
						left.getName());
	}
	public static final String ISLOCAL_METHOD_NAME = "isLocal";
	public static boolean isIsLocalMethod(Method method){
		if(null == method){
			return false;
		}
		return method.getName().equals(ISLOCAL_METHOD_NAME)
				&& method.getParameterTypes().length == 0 
				&& method.getExceptionTypes().length == 0
				&& method.getReturnType() == boolean.class;
	}
	protected static void injectMethods(ThriftStructMetadata metadata,
			Map<Object, TypeValue> data,
			Object instance, 
			BaseTypeTransformer transformer, 
			boolean useId) throws Exception{
        List<ThriftMethodInjection> methodInjections = Lists.newArrayList(metadata.getMethodInjections());
        final List<ThriftMethodInjection> builtinInjections = Lists.newArrayList();
        Class<?>baseBeanClass =getBaseBeanClass();
        if(null != baseBeanClass && baseBeanClass.isInstance(instance)){        	
        	// 将BaseBean内置字段排除保存到 builtinInjections
        	for(Iterator<ThriftMethodInjection> itor = methodInjections.iterator();itor.hasNext();){
        		ThriftMethodInjection input = itor.next();
        		if(BaseThriftUtils.BASEBEAN_BUILTIN_FIELDS.contains(input.getParameters().get(0).getName())){
        			builtinInjections.add(input);
        			itor.remove();
        		}
        	}
        }
        
        // inject methods
        injectMethods(data,instance,methodInjections, transformer, useId);
        // 所有其他字段都执行完成注入后再执行内置字段的注入
        injectMethods(data,instance,builtinInjections, transformer, useId);
	}
	private static void injectMethods(
			Map<Object, TypeValue> data,
			Object instance,
			Iterable<ThriftMethodInjection> methodInjections, 
			BaseTypeTransformer transformer, 
			boolean useId) throws Exception{
		for (ThriftMethodInjection methodInjection : methodInjections) {
	        boolean shouldInvoke = false;
	        Object[] parametersValues = new Object[methodInjection.getParameters().size()];
	        Type[] srcTypes = new Type[methodInjection.getParameters().size()];
	        for (ThriftParameterInjection parameter : methodInjection.getParameters()) {
	        	TypeValue value = data.get(useId ?parameter.getId():parameter.getName());
	            if (value != null) {
	                parametersValues[parameter.getParameterIndex()] = value.value;
	                srcTypes[parameter.getParameterIndex()] = value.type;
	                shouldInvoke = true;
	            }
	        }
	
	        if (shouldInvoke) {
	            try {	            	
	            	Method method = methodInjection.getMethod();
	            	Type[] parameterTypes = method.getGenericParameterTypes();
	            	for(int i = 0 ;i<parametersValues.length;++i){
	            		parametersValues[i]=transformer.cast(parametersValues[i], srcTypes[i], parameterTypes[i]);
	            	}
	            	method.invoke(instance, parametersValues);
	            }
	            catch (InvocationTargetException e) {
	                if (e.getTargetException() != null) {
	                	Throwables.throwIfUnchecked(e.getTargetException());
	                	throw new RuntimeException(e.getTargetException());
	                }
	                throw e;
	            }
	        }
	    }
	}
	/**
	 * 填充{@code instance}实例的字段<br>
	 * 参见 com.facebook.swift.codec.internal.reflection.ReflectionThriftStructCodec#constructStruct(Map<Short, Object>)
	 * @param data
	 * @param metadata
	 * @param instance
	 * @return instance always
	 * @throws Exception
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected static <T>T fillStructField(Map<Object, TypeValue> data,
			ThriftStructMetadata metadata,
			T instance,
			BaseTypeTransformer transformer,
			boolean useId) 
		throws Exception{
		checkArgument(null != instance,"instance is null");
		// inject fields
	    for (ThriftFieldMetadata fieldMetadata : metadata.getFields(THRIFT_FIELD)) {
	        for (ThriftInjection injection : fieldMetadata.getInjections()) {
	            if (injection instanceof ThriftFieldInjection) {
	                ThriftFieldInjection fieldInjection = (ThriftFieldInjection) injection;	                
	                TypeValue value = data.get(useId ?fieldInjection.getId():fieldInjection.getName());
	                if (value != null) {
	                	Field f = fieldInjection.getField();
	                    f.set(instance, transformer.cast(value.value,value.type,f.getGenericType()));
	                }
	            }
	        }
	    }
	
	    // inject methods
	    injectMethods(metadata, (Map)data, instance, transformer, useId);
	    // builder method
	    if (metadata.getBuilderMethod().isPresent()) {
	        ThriftMethodInjection builderMethod = metadata.getBuilderMethod().get();
	        Object[] parametersValues = new Object[builderMethod.getParameters().size()];
	        for (ThriftParameterInjection parameter : builderMethod.getParameters()) {
	            TypeValue value = data.get(useId ?parameter.getId():parameter.getName());
	            parametersValues[parameter.getParameterIndex()] = value.value;
	        }
	
	        try {
	            instance = (T) builderMethod.getMethod().invoke(instance, parametersValues);
	            if (instance == null) {
	                throw new IllegalArgumentException("Builder method returned a null instance");
	
	            }
	            if (!metadata.getStructClass().isInstance(instance)) {
	                throw new IllegalArgumentException(format("Builder method returned instance of type %s, but an instance of %s is required",
	                        instance.getClass().getName(),
	                        metadata.getStructClass().getName()));
	            }
	        }
	        catch (InvocationTargetException e) {
	            if (e.getTargetException() != null) {
	            	Throwables.throwIfUnchecked(e.getTargetException());
	            	throw new RuntimeException(e.getTargetException());
	            }
	            throw e;
	        }
	    }
	    return (T) instance;
	}

}
